/*
 * Copyright (c) 2003, 2004, 2005, 2006 Israfil Consulting Services Corporation
 * Copyright (c) 2003, 2004, 2005, 2006 Christian Edward Gruber
 * All Rights Reserved
 * 
 * This software is licensed under the Berkeley Standard Distribution license,
 * (BSD license), as defined below:
 * 
 * Redistribution and use in source and binary forms, with or without 
 * modification, are permitted provided that the following conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright notice, this 
 *    list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright notice, 
 *    this list of conditions and the following disclaimer in the documentation 
 *    and/or other materials provided with the distribution.
 * 3. Neither the name of Israfil Consulting Services nor the names of its contributors 
 *    may be used to endorse or promote products derived from this software without 
 *    specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 
 * IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, 
 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, 
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, 
 * OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, 
 * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) 
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY 
 * OF SUCH DAMAGE.
 * 
 * $Id: MockResultSet.java 517M 2007-07-09 20:00:15Z (local) $
 */
package net.israfil.foundation.mock.sql;

import java.io.InputStream;
import java.io.OutputStream;
import java.io.Reader;
import java.math.BigDecimal;
import java.net.URL;
import java.sql.Array;
import java.sql.Blob;
import java.sql.Clob;
import java.sql.Date;
import java.sql.Ref;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.SQLWarning;
import java.sql.Statement;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Calendar;
import java.util.List;
import java.util.Map;

import net.israfil.foundation.mock.sql.MockResultSetMetaData.ColSpec;

/**
 * A Mock implementation of ResultSet that takes an array of column
 * names and an array or rows, where rows are arrays of data.  A Mock
 * ResultSet can be used in unit testing where database access needs
 * to be stubbed out.
 * 
 * @author <a href="mailto:cgruber@israfil.net">Christian Edward Gruber </a>
 */
public class MockResultSet implements ResultSet {
	
	public MockResultSetMetaData metadata;
    
    protected List<Row> data;
    public List<Row> getData() { return data; } 
    int cursorPosition = 0;
    boolean insertRow = false;
    private final static int BEFOREFIRST = 0;
    private final int AFTERLAST;
    protected int concurrency = CONCUR_READ_ONLY;
	protected int fetchDirection = FETCH_UNKNOWN;
	protected int fetchSize = 0;
	protected Statement statement = null;
	protected boolean wasNull = false;
	boolean open = false;

    private MockResultSet(int size) {
        AFTERLAST = size+1;
    }
    
    /**
     *  The main constructor, which takes an array of strings representing
     *  the columns of the supposed DataReader, and an array of string arrays
     *  representing the row data.  With this, one can simulate a call to the
     *  database.
     *
     * @param columns the columns to be simulated
     * @param rows the rows to be simulated
     * 
     */
    public MockResultSet(String[] columns, Object[][] rows) {
    	this((rows==null)?0:rows.length);
        metadata = new MockResultSetMetaData(columns);
        primeRows(rows);
    }
    
    /**
     *  The main constructor, which takes an array of strings representing
     *  the columns of the supposed DataReader, and an array of string arrays
     *  representing the row data.  With this, one can simulate a call to the
     *  database.
     *
     * @param columns the columns to be simulated
     * @param rows the rows to be simulated
     * 
     */
    public MockResultSet(ColSpec[] columns, Object[][] rows) {
    	this((rows==null)?0:rows.length);
        metadata = new MockResultSetMetaData(columns);
        primeRows(rows);
    }
    
    /**
     * Private method that takes given row data and populates the internal
     * structures with said data.
     * @param rows
     */
    private void primeRows(Object[][] rows) {
        if (rows == null) rows = new String[][]{};
        try {
	        data =  new ArrayList<Row>(metadata.getColumnCount());
	        for (int rowNum = 0; rowNum < rows.length; rowNum++) {
	            data.add(new Row(metadata,new ArrayList<Object>(Arrays.asList(rows[rowNum]))));
	        }
        } catch (SQLException e) {
        	throw new RuntimeException(e);
        }
        cursorPosition = 0;
        open = true;
    }
    
    public void finalize() throws Throwable {
		data = null;
		metadata = null;
    	super.finalize();
    }
    
	/**
	 * Moves the cursor to the given row number in this ResultSet object.
     *
     * If the row number is positive, the cursor moves to the given row number 
     * with respect to the beginning of the result set. The first row is row 1, 
     * the second is row 2, and so on.
     *
     * If the given row number is negative, the cursor moves to an absolute row 
     * position with respect to the end of the result set. For example, calling 
     * the method absolute(-1) positions the cursor on the last row; calling the 
     * method absolute(-2) moves the cursor to the next-to-last row, and so on.
     *
     * An attempt to position the cursor beyond the first/last row in the result 
     * set leaves the cursor before the first row or after the last row.
     *
     * Note: Calling absolute(1) is the same as calling first(). Calling 
     * absolute(-1) is the same as calling last().
     * 
     * @param row - the number of the row to which the cursor should move. A positive number indicates the row number counting from the beginning of the result set; a negative number indicates the row number counting from the end of the result set 
     * @return true if the cursor is on the result set; false otherwise 
	 */
	public boolean absolute(int row) throws SQLException {
		// the number of the row to which the cursor should move. 
		// A positive number indicates the row number counting from 
		// the beginning of the result set; a negative number indicates 
		// the row number counting from the end of the result set.
		if (row > 0) {
			if (BEFOREFIRST + row < AFTERLAST) {
				cursorPosition = BEFOREFIRST + row;
				return true;
			} else {
				cursorPosition = AFTERLAST;
				return false;
			}
		} else if (row < 0) {
			if (AFTERLAST + row > BEFOREFIRST) {
				cursorPosition = AFTERLAST + row;
				return true;
			} else {
				cursorPosition = BEFOREFIRST;
				return false;
			}			
		} else /* offset == 0 */ { 
			// This is essentially beforeFirst();
			cursorPosition = 0;
			return false;
		}
	}

	public void afterLast() throws SQLException {
		absolute(data.size()+1);
	}

	public void beforeFirst() throws SQLException {
		absolute(0);
	}

	public void cancelRowUpdates() throws SQLException {
		throw new UnsupportedOperationException("Not yet implemented.");
	}

	public void clearWarnings() throws SQLException {
		throw new UnsupportedOperationException("Not yet implemented.");
	}

	public void close() throws SQLException {
		open = false;
	}

	public void deleteRow() throws SQLException {
		if (insertRow) throw new SQLException("Cannot delete the insert row.");
		throw new UnsupportedOperationException("Not yet implemented.");
	}

	public int findColumn(String column) throws SQLException {
		throw new UnsupportedOperationException("Not yet implemented.");
	}

	public boolean first() throws SQLException {
		return absolute(1);
	}

	public Object getInternalValue(int rowNumber, int column) { 
		try {
			return processAccessedObject(data.get(rowNumber-1).get(column));
		} catch (SQLException e) { throw new RuntimeException(e); }
	}
	
	public Object getInternalValue(int rowNumber, String column) { 
		try {
			return processAccessedObject(data.get(rowNumber-1).get(column));
		} catch (SQLException e) { throw new RuntimeException(e); }
	}
	
	public Object processAccessedObject(Object o) {
		if (o == null) wasNull = true;
		return o;
	}
	
	public Array getArray(int column) throws SQLException {
		return (Array)getInternalValue(cursorPosition,column);
	}

	public Array getArray(String column) throws SQLException {
		return (Array)getInternalValue(cursorPosition,column);
	}

	public InputStream getAsciiStream(int column) throws SQLException {
		return (InputStream)getInternalValue(cursorPosition,column);
	}

	public InputStream getAsciiStream(String column) throws SQLException {
		return (InputStream)getInternalValue(cursorPosition,column);
	}

	public BigDecimal getBigDecimal(int column, int scale) throws SQLException {
		Object o = getInternalValue(cursorPosition,column);
		if (o == null) return null;
		BigDecimal bd;
		if (o instanceof String) bd = new BigDecimal((String)o);
		else if (o instanceof Number) bd = new BigDecimal(((Number)o).doubleValue());
		else if (o instanceof java.util.Date) bd = new BigDecimal(((java.util.Date)o).getTime());
		else if (o instanceof Calendar) bd = new BigDecimal(((Calendar)o).getTimeInMillis());
		else if (o instanceof char[]) bd = new BigDecimal(((char[])o));
		else if (o instanceof Boolean) return new BigDecimal(((Boolean)o).booleanValue() ? 1 : 0);
		else bd = new BigDecimal(String.valueOf(o));
		bd.setScale(scale);
		return bd;
	}

	public BigDecimal getBigDecimal(int column) throws SQLException {
		Object o = getInternalValue(cursorPosition,column);
		if (o == null) return null;
		BigDecimal bd;
		if (o instanceof String) bd = new BigDecimal((String)o);
		else if (o instanceof Number) bd = new BigDecimal(((Number)o).doubleValue());
		else if (o instanceof java.util.Date) bd = new BigDecimal(((java.util.Date)o).getTime());
		else if (o instanceof Calendar) bd = new BigDecimal(((Calendar)o).getTimeInMillis());
		else if (o instanceof char[]) bd = new BigDecimal(((char[])o));
		else if (o instanceof Boolean) return new BigDecimal(((Boolean)o).booleanValue() ? 1 : 0);
		else bd = new BigDecimal(String.valueOf(o));
		return bd;
	}

	public BigDecimal getBigDecimal(String column, int scale) throws SQLException {
		Object o = getInternalValue(cursorPosition,column);
		if (o == null) return null;
		BigDecimal bd;
		if (o instanceof String) bd = new BigDecimal((String)o);
		else if (o instanceof Number) bd = new BigDecimal(((Number)o).doubleValue());
		else if (o instanceof java.util.Date) bd = new BigDecimal(((java.util.Date)o).getTime());
		else if (o instanceof Calendar) bd = new BigDecimal(((Calendar)o).getTimeInMillis());
		else if (o instanceof char[]) bd = new BigDecimal(((char[])o));
		else if (o instanceof Boolean) return new BigDecimal(((Boolean)o).booleanValue() ? 1 : 0);
		else bd = new BigDecimal(String.valueOf(o));
		bd.setScale(scale);
		return bd;
	}

	public BigDecimal getBigDecimal(String column) throws SQLException {
		Object o = getInternalValue(cursorPosition,column);
		if (o == null) return null;
		BigDecimal bd;
		if (o instanceof String) bd = new BigDecimal((String)o);
		else if (o instanceof Number) bd = new BigDecimal(((Number)o).doubleValue());
		else if (o instanceof java.util.Date) bd = new BigDecimal(((java.util.Date)o).getTime());
		else if (o instanceof Calendar) bd = new BigDecimal(((Calendar)o).getTimeInMillis());
		else if (o instanceof char[]) bd = new BigDecimal(((char[])o));
		else if (o instanceof Boolean) return new BigDecimal(((Boolean)o).booleanValue() ? 1 : 0);
		else bd = new BigDecimal(String.valueOf(o));
		return bd;
	}

	public InputStream getBinaryStream(int column) throws SQLException {
		return (InputStream)getInternalValue(cursorPosition,column);
	}

	public InputStream getBinaryStream(String column) throws SQLException {
		return (InputStream)getInternalValue(cursorPosition,column);
	}

	public Blob getBlob(int column) throws SQLException {
		return new MockBlob(getBytes(column));
	}

	public Blob getBlob(String column) throws SQLException {
		return new MockBlob(getBytes(column));
	}

	public boolean getBoolean(int column) throws SQLException {
		if (getInternalValue(cursorPosition,column) == null) return false; 
		Object o = getInternalValue(cursorPosition,column);
		if (o instanceof Number) return ((Number)o).byteValue() != 0;
		else return Boolean.valueOf(String.valueOf(o));
	}

	public boolean getBoolean(String column) throws SQLException {
		if (getInternalValue(cursorPosition,column) == null) return false; 
		Object o = getInternalValue(cursorPosition,column);
		if (o instanceof Number) return ((Number)o).byteValue() != 0;
		else return Boolean.valueOf(String.valueOf(o));
	}

	public byte getByte(int column) throws SQLException { 
		if (getInternalValue(cursorPosition,column) == null) return 0; 
		Object o = getInternalValue(cursorPosition,column);
		if (o instanceof Number) {
			return ((Number)o).byteValue();
		} else if (o instanceof Boolean) {
			return ((Boolean)o).booleanValue() ? (byte)1 : (byte)0 ;
		} else return Byte.valueOf(String.valueOf(o));
	}

	public byte getByte(String column) throws SQLException {
		if (getInternalValue(cursorPosition,column) == null) return 0; 
		Object o = getInternalValue(cursorPosition,column);
		if (o instanceof Number) {
			return ((Number)o).byteValue();
		} else if (o instanceof Boolean) {
			return ((Boolean)o).booleanValue() ? (byte)1 : (byte)0 ;
		} else return Byte.valueOf(String.valueOf(o));
	}

	public byte[] getBytes(int column) throws SQLException {
		throw new UnsupportedOperationException("Not yet implemented.");
	}

	public byte[] getBytes(String column) throws SQLException {
		throw new UnsupportedOperationException("Not yet implemented.");
	}

	public Reader getCharacterStream(int column) throws SQLException {
		return (Reader)getInternalValue(cursorPosition,column);
	}

	public Reader getCharacterStream(String column) throws SQLException {
		return (Reader)getInternalValue(cursorPosition,column);
	}

	public Clob getClob(int column) throws SQLException {
		return (Clob)getInternalValue(cursorPosition,column);
	}

	public Clob getClob(String column) throws SQLException {
		return (Clob)getInternalValue(cursorPosition,column);
	}

	public int getConcurrency() throws SQLException {
		return concurrency;
	}

	public void setConcurrency(int concurrency) {
		this.concurrency = concurrency;
	}
	public String getCursorName() throws SQLException {
		throw new UnsupportedOperationException("Not yet implemented.");
	}

	public Date getDate(int column, Calendar cal) throws SQLException {
		throw new UnsupportedOperationException("Not yet implemented.");
	}

	public Date getDate(int column) throws SQLException {
		Object o = getInternalValue(cursorPosition,column);
		if (o == null) return null;
		if (o instanceof Date) return (Date)o;
		if (o instanceof java.util.Date) 
			return new Date(((java.util.Date)o).getTime());
		if (o instanceof Calendar) 
			return new Date(((Calendar)o).getTimeInMillis());
		return Date.valueOf(String.valueOf(o));
	}

	public Date getDate(String column, Calendar cal) throws SQLException {
		throw new UnsupportedOperationException("Not yet implemented.");
	}

	public Date getDate(String column) throws SQLException {
		Object o = getInternalValue(cursorPosition,column);
		if (o == null) return null;
		if (o instanceof Date) return (Date)o;
		if (o instanceof java.util.Date) 
			return new Date(((java.util.Date)o).getTime());
		if (o instanceof Calendar) 
			return new Date(((Calendar)o).getTimeInMillis());
		return Date.valueOf(String.valueOf(o));
	}

	public double getDouble(int column) throws SQLException {
		if (getInternalValue(cursorPosition,column) == null) return 0; 
		Object o = getInternalValue(cursorPosition,column);
		if (o instanceof Number) {
			return ((Number)o).doubleValue();
		} else if (o instanceof Boolean) {
			return ((Boolean)o).booleanValue() ? 1 : 0 ;
		} else return Double.valueOf(String.valueOf(o));
	}

	public double getDouble(String column) throws SQLException {
		if (getInternalValue(cursorPosition,column) == null) return 0; 
		Object o = getInternalValue(cursorPosition,column);
		if (o instanceof Number) {
			return ((Number)o).doubleValue();
		} else if (o instanceof Boolean) {
			return ((Boolean)o).booleanValue() ? 1 : 0 ;
		} else return Double.valueOf(String.valueOf(o));
	}

	public int getFetchDirection() throws SQLException {
		throw new UnsupportedOperationException("Not yet implemented.");
	}

	public int getFetchSize() throws SQLException {
		throw new UnsupportedOperationException("Not yet implemented.");
	}

	public float getFloat(int column) throws SQLException {
		if (getInternalValue(cursorPosition,column) == null) return 0; 
		Object o = getInternalValue(cursorPosition,column);
		if (o instanceof Number) {
			return ((Number)o).floatValue();
		} else if (o instanceof Boolean) {
			return ((Boolean)o).booleanValue() ? 1 :0 ;
		} else return Float.valueOf(String.valueOf(o));
	}

	public float getFloat(String column) throws SQLException {
		if (getInternalValue(cursorPosition,column) == null) return 0; 
		Object o = getInternalValue(cursorPosition,column);
		if (o instanceof Number) {
			return ((Number)o).floatValue();
		} else if (o instanceof Boolean) {
			return ((Boolean)o).booleanValue() ? 1 :0 ;
		} else return Float.valueOf(String.valueOf(o));
	}

	public int getInt(int column) throws SQLException {
		if (getInternalValue(cursorPosition,column) == null) return 0; 
		Object o = getInternalValue(cursorPosition,column);
		if (o instanceof Number) {
			return ((Number)o).intValue();
		} else if (o instanceof Boolean) {
			return ((Boolean)o).booleanValue() ? 1 :0 ;
		} else return Integer.valueOf(String.valueOf(o));
	}

	public int getInt(String column) throws SQLException {
		if (getInternalValue(cursorPosition,column) == null) return 0; 
		Object o = getInternalValue(cursorPosition,column);
		if (o instanceof Number) {
			return ((Number)o).intValue();
		} else if (o instanceof Boolean) {
			return ((Boolean)o).booleanValue() ? 1 :0 ;
		} else return Integer.valueOf(String.valueOf(o));
	}

	public long getLong(int column) throws SQLException {
		if (getInternalValue(cursorPosition,column) == null) return 0; 
		Object o = getInternalValue(cursorPosition,column);
		if (o instanceof Number) {
			return ((Number)o).longValue();
		} else if (o instanceof Boolean) {
			return ((Boolean)o).booleanValue() ? 1 :0 ;
		} else return Long.valueOf(String.valueOf(o));
	}

	public long getLong(String column) throws SQLException {
		if (getInternalValue(cursorPosition,column) == null) return 0; 
		Object o = getInternalValue(cursorPosition,column);
		if (o instanceof Number) {
			return ((Number)o).longValue();
		} else if (o instanceof Boolean) {
			return ((Boolean)o).booleanValue() ? 1 :0 ;
		} else return Long.valueOf(String.valueOf(o));
	}

	public ResultSetMetaData getMetaData() throws SQLException {
		return metadata;
	}

	public Object getObject(int column, Map<String, Class<?>> mapping) throws SQLException {
		throw new UnsupportedOperationException("Unimplented feature.");
	}

	public Object getObject(int column) throws SQLException {
		return (Object)getInternalValue(cursorPosition,column);
	}

	public Object getObject(String column, Map<String, Class<?>> mapping) throws SQLException {
		throw new UnsupportedOperationException("Unimplented feature.");
	}

	public Object getObject(String column) throws SQLException {
		return (Object)getInternalValue(cursorPosition,column);
	}

	public Ref getRef(int column) throws SQLException {
		throw new UnsupportedOperationException("Unimplented feature.");
	}

	public Ref getRef(String column) throws SQLException {
		throw new UnsupportedOperationException("Unimplented feature.");
	}

	public int getRow() throws SQLException {
		return (cursorPosition > BEFOREFIRST && cursorPosition < AFTERLAST) ? cursorPosition : 0;
	}

	public short getShort(int column) throws SQLException {
		if (getInternalValue(cursorPosition,column) == null) return 0; 
		Object o = getInternalValue(cursorPosition,column);
		if (o instanceof Number) {
			return ((Number)o).shortValue();
		} else if (o instanceof Boolean) {
			return ((Boolean)o).booleanValue() ? (short)1 : (short)0 ;
		} else return Short.valueOf(String.valueOf(o));
	}

	public short getShort(String column) throws SQLException {
		if (getInternalValue(cursorPosition,column) == null) return 0; 
		Object o = getInternalValue(cursorPosition,column);
		if (o instanceof Number) {
			return ((Number)o).shortValue();
		} else if (o instanceof Boolean) {
			return ((Boolean)o).booleanValue() ? (short)1 : (short)0 ;
		} else return Short.valueOf(String.valueOf(o));
	}

	public Statement getStatement() throws SQLException {
		return null;
	}

	public String getString(int column) throws SQLException {
		Object o = getInternalValue(cursorPosition,column);
		return (o == null) ? null : String.valueOf(o);
	}

	public String getString(String column) throws SQLException {
		Object o = getInternalValue(cursorPosition,column);
		return (o == null) ? null : String.valueOf(o);
	}

	public Time getTime(int column, Calendar cal) throws SQLException {
		return (Time)getInternalValue(cursorPosition,column);
	}

	public Time getTime(int column) throws SQLException {
		Object o = getInternalValue(cursorPosition,column);
		if (o == null) return null;
		if (o instanceof Time) return (Time)o;
		if (o instanceof java.util.Date) 
			return new Time(((java.util.Date)o).getTime());
		if (o instanceof Calendar) 
			return new Time(((Calendar)o).getTimeInMillis());
		return Time.valueOf(String.valueOf(o));
	}

	public Time getTime(String column, Calendar cal) throws SQLException {
		return (Time)getInternalValue(cursorPosition,column);
	}

	public Time getTime(String column) throws SQLException {
		Object o = getInternalValue(cursorPosition,column);
		if (o == null) return null;
		if (o instanceof Time) return (Time)o;
		if (o instanceof java.util.Date) 
			return new Time(((java.util.Date)o).getTime());
		if (o instanceof Calendar) 
			return new Time(((Calendar)o).getTimeInMillis());
		return Time.valueOf(String.valueOf(o));
	}

	public Timestamp getTimestamp(int column, Calendar cal) throws SQLException {
		return (Timestamp)getInternalValue(cursorPosition,column);
	}

	public Timestamp getTimestamp(int column) throws SQLException {
		Object o = getInternalValue(cursorPosition,column);
		if (o == null) return null;
		if (o instanceof Timestamp) return (Timestamp)o;
		if (o instanceof java.util.Date) 
			return new Timestamp(((java.util.Date)o).getTime());
		if (o instanceof Calendar) 
			return new Timestamp(((Calendar)o).getTimeInMillis());
		return Timestamp.valueOf(String.valueOf(o));
	}

	public Timestamp getTimestamp(String column, Calendar cal) throws SQLException {
		return (Timestamp)getInternalValue(cursorPosition,column);
	}

	public Timestamp getTimestamp(String column) throws SQLException {
		Object o = getInternalValue(cursorPosition,column);
		if (o == null) return null;
		if (o instanceof Timestamp) return (Timestamp)o;
		if (o instanceof java.util.Date) 
			return new Timestamp(((java.util.Date)o).getTime());
		if (o instanceof Calendar) 
			return new Timestamp(((Calendar)o).getTimeInMillis());
		return Timestamp.valueOf(String.valueOf(o));
	}

	public int getType() throws SQLException {
		return TYPE_SCROLL_INSENSITIVE;
	}

	public InputStream getUnicodeStream(int column) throws SQLException {
		return (InputStream)getInternalValue(cursorPosition,column);
	}

	public InputStream getUnicodeStream(String column) throws SQLException {
		return (InputStream)getInternalValue(cursorPosition,column);
	}

	public URL getURL(int column) throws SQLException {
		return (URL)getInternalValue(cursorPosition,column);
	}

	public URL getURL(String column) throws SQLException {
		return (URL)getInternalValue(cursorPosition,column);
	}

	public SQLWarning getWarnings() throws SQLException {
		// TODO Auto-generated method stub
		return null;
	}

	public void insertRow() throws SQLException {
		if (!insertRow) throw new SQLException("Cannot insert any row except the insert row.");
		// TODO: Add logic to check nullability.
		
	}

	public boolean isAfterLast() throws SQLException {
		return cursorPosition >= AFTERLAST;
	}

	public boolean isBeforeFirst() throws SQLException {
		return cursorPosition <= BEFOREFIRST;
	}

	public boolean isFirst() throws SQLException {
		return data.size() > 0 && !insertRow && cursorPosition == BEFOREFIRST + 1;
	}

	public boolean isLast() throws SQLException {
		return data.size() > 0 && !insertRow && cursorPosition == AFTERLAST - 1;
	}

	public boolean last() throws SQLException {
		return absolute(-1);
	}

	public void moveToCurrentRow() throws SQLException {
		insertRow = false;
	}

	public void moveToInsertRow() throws SQLException {
		insertRow = true;
	}

	public boolean next() throws SQLException {
		return relative(1);
	}

	public boolean previous() throws SQLException {
		return relative(-1);
	}

	public void refreshRow() throws SQLException {
		// TODO: blow away update row
		
	}

	public boolean relative(int offset) throws SQLException {
		return absolute(cursorPosition+offset);
	}

	public boolean rowDeleted() throws SQLException {
		// TODO: Possibly implement deletion detection
		return false;
	}

	public boolean rowInserted() throws SQLException {
		// TODO: Possibly implement insertion detection
		return false;
	}

	public boolean rowUpdated() throws SQLException {
		// TODO: Possibly implement update detection.
		return false;
	}

	public void setFetchDirection(int direction) throws SQLException {
		this.fetchDirection = direction;
	}

	public void setFetchSize(int rows) throws SQLException {
		if (rows < 0 || (this.statement != null && rows > statement.getMaxRows())) 
			throw new SQLException("Fetch size less than zero or greater than statement's maximum rows.");
		else this.fetchSize = rows;
	}

	public void updateArray(int column, Array x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateArray(String column, Array x) throws SQLException {
		updateArray(findColumn(column),x);
	}

	public void updateAsciiStream(int column, InputStream x, int length) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateAsciiStream(String column, InputStream x, int length) throws SQLException {
		updateAsciiStream(findColumn(column),x,length);
	}

	public void updateBigDecimal(int column, BigDecimal x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateBigDecimal(String column, BigDecimal x) throws SQLException {
		updateBigDecimal(findColumn(column),x);
	}

	public void updateBinaryStream(int column, InputStream x, int length) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateBinaryStream(String column, InputStream x, int length) throws SQLException {
		updateBinaryStream(findColumn(column),x,length);
	}

	public void updateBlob(int column, Blob x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateBlob(String column, Blob x) throws SQLException {
		updateBlob(findColumn(column),x);
	}

	public void updateBoolean(int column, boolean x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateBoolean(String column, boolean x) throws SQLException {
		updateBoolean(findColumn(column),x);
	}

	public void updateByte(int column, byte x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateByte(String column, byte x) throws SQLException {
		updateByte(findColumn(column),x);
	}

	public void updateBytes(int column, byte[] x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateBytes(String column, byte[] x) throws SQLException {
		updateBytes(findColumn(column),x);
	}

	public void updateCharacterStream(int column, Reader x, int length) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateCharacterStream(String column, Reader reader, int length) throws SQLException {
		updateCharacterStream(findColumn(column),reader,length);
	}

	public void updateClob(int column, Clob x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateClob(String column, Clob x) throws SQLException {
		updateClob(findColumn(column),x);
	}

	public void updateDate(int column, Date x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateDate(String column, Date x) throws SQLException {
		updateDate(findColumn(column),x);
	}

	public void updateDouble(int column, double x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateDouble(String column, double x) throws SQLException {
		updateDouble(findColumn(column),x);
	}

	public void updateFloat(int column, float x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateFloat(String column, float x) throws SQLException {
		updateFloat(findColumn(column),x);
	}

	public void updateInt(int column, int x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateInt(String column, int x) throws SQLException {
		updateInt(findColumn(column),x);
	}

	public void updateLong(int column, long x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateLong(String column, long x) throws SQLException {
		updateLong(findColumn(column),x);
	}

	public void updateNull(int column) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateNull(String column) throws SQLException {
		updateNull(findColumn(column));
	}

	public void updateObject(int column, Object x, int scale) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateObject(int column, Object x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateObject(String column, Object x, int scale) throws SQLException {
		updateObject(findColumn(column),x,scale);
	}

	public void updateObject(String column, Object x) throws SQLException {
		updateObject(findColumn(column),x);
	}

	public void updateRef(int column, Ref x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateRef(String column, Ref x) throws SQLException {
		updateRef(findColumn(column),x);
	}

	public void updateRow() throws SQLException {
		// TODO: Implement update call.
		throw new UnsupportedOperationException("Not yet implemented");
	}

	public void updateShort(int column, short x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateShort(String column, short x) throws SQLException {
		updateShort(findColumn(column),x);
	}

	public void updateString(int column, String x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateString(String column, String x) throws SQLException {
		updateString(findColumn(column),x);
	}

	public void updateTime(int column, Time x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateTime(String column, Time x) throws SQLException {
		updateTime(findColumn(column),x);
	}

	public void updateTimestamp(int column, Timestamp x) throws SQLException {
		// TODO Auto-generated method stub
		
	}

	public void updateTimestamp(String column, Timestamp x) throws SQLException {
		updateTimestamp(findColumn(column),x);
	}

	public boolean wasNull() throws SQLException {
		// TODO: Implement very awkward wasNull() method.
		return false;
	}
	

	class MockBlob implements Blob {
		
		private byte[] bytes;
		
		public MockBlob(byte[] bytes) {
			this.bytes=bytes;
		}

		public InputStream getBinaryStream() throws SQLException {
			// TODO Implement getBinaryStream()
			return null;
		}

		public byte[] getBytes(long pos, int length) throws SQLException {
			// TODO Implement getBinaryStream()
			if (false) bytes.hashCode();
			return null;
		}

		public long length() throws SQLException {
			// TODO Implement length()
			return 0;
		}

		public long position(Blob pattern, long start) throws SQLException {
			// TODO Implement position(Blob,long)
			return 0;
		}

		public long position(byte[] pattern, long start) throws SQLException {
			// TODO Implement position(byte[],long)
			return 0;
		}

		public OutputStream setBinaryStream(long pos) throws SQLException {
			// TODO Implement setBinaryStream(long)
			return null;
		}

		public int setBytes(long pos, byte[] bytes, int offset, int len) throws SQLException {
			// TODO Implement setBytes(long,byte[],int,int)
			return 0;
		}

		public int setBytes(long pos, byte[] bytes) throws SQLException {
			// TODO Implement setBytes(long,byte[])
			return 0;
		}

		public void truncate(long len) throws SQLException {
			// TODO Implement truncate(long)
		}	
	};
	
}
